#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <new>
#include <cstdio>
#include <cassert>
#define MEMORY_ALIGN_MASK 31UL

#include "memory_cpp.hpp"
#include "htab_cpp.hpp"

/*layer 0: Aligned allocator with at least some bytes before allocated pointer*/
inline void *adjust_ptr(void *in, size_t offset) {
  __intptr_t baseptr = (__intptr_t)in + offset;
  return (void *)(baseptr + MEMORY_ALIGN_MASK & ~MEMORY_ALIGN_MASK);
}
static void *esmd_aligned_malloc(size_t size) {
  void *raw = malloc(size + sizeof(meminfo_t) + MEMORY_ALIGN_MASK);
  #ifdef __sw__
  for (int i = 0; i < size / 8; i ++){
    long ran;
    asm volatile("rtc %0\n" : "=r"(ran));
    ((long*)raw)[i] = ran;
  }
  #endif
  if (raw == 0)
    return (void *)raw;
  void *ret = adjust_ptr(raw, sizeof(meminfo_t));
  meminfo_t *info = (meminfo_t *)ret - 1;
  info->raw = raw;
  info->size = size;
  info->rec = NULL;
  return ret;
}

namespace esmd {
/*layer 0.5: Malloc and free with memrec support*/
void deallocate(void *ptr) {
  meminfo_t *info = (meminfo_t *)ptr - 1;
  if (info->rec != NULL) {
    info->rec->size -= info->size;
    info->rec->cnt -= 1;
  }
  free(info->raw);
}

void *allocate(size_t size, memrec_t *rec) {
  void *ret = esmd_aligned_malloc(size);
  if (ret == NULL) {
    printf("Failed to allocate %ld bytes!", size);
    print_memusage();
    abort();
  }
  meminfo_t *info = (meminfo_t *)ret - 1;
  info->rec = rec;
  rec->size += info->size;
  rec->cnt += 1;
  if (rec->size > rec->peak) rec->peak = rec->size;
  assert(ret);
  return ret;
}

void *reallocate_raw(void *ptr, size_t size) {
  meminfo_t *info = (meminfo_t *)ptr - 1;
  if (info->size >= size) {
    return ptr;
  } else {
    void *new_place = allocate(size, info->rec);
    memcpy(new_place, ptr, info->size);
    deallocate(ptr);
    return new_place;
  }
}
/* layer3 : name based memory management */
__attribute__((init_priority(101)))
memrec_t self_rec = {0, 0};

__attribute__((init_priority(101)))
htab<cchar_key, memrec_t *> rec_tab(&self_rec);
__attribute__((init_priority(101)))
pool_t<memrec_t> rec_pool(&self_rec);

memrec_t *generate_memrec() {
  memrec_t *rec = rec_pool.get();
  rec->cnt = 0;
  rec->size = 0;
  rec->peak = 0;
  return rec;
}
memrec_t *get_memrec(const char *name) {
  return rec_tab.setdefault(name, generate_memrec);
}

__always_inline void print_header(FILE *f) {
  char dashes[49];
  memset(dashes, '-', 49);
  fprintf(f, "+%-.32s+%.10s+%.16s+%.16s+\n", dashes, dashes, dashes, dashes);
  fprintf(f, "|%-32.32s|%10.10s|%16.16s|%16.16s|\n", "memory block name", "#active", "active size", "peak size");
  fprintf(f, "+%-.32s+%.10s+%.16s+%.16s+\n", dashes, dashes, dashes, dashes);
}
void print_memusage(FILE *f) {
  print_header(f);
  fprintf(f, "|%-32.32s|%10ld|%16ld|%16ld|\n", "memory table", self_rec.cnt, self_rec.size, self_rec.peak);
  //for (auto ite = esmd::rec_tab.begin(); ite != esmd::rec_tab.end(); ite++) {
  for (auto &rec : rec_tab){
    fprintf(f, "|%-32.32s|%10ld|%16ld|%16ld|\n", rec.key, rec.val->cnt, rec.val->size, rec.val->peak);
    // fprintf(f, "%s: %d %d %d\n", (*ite).key, (*ite).val->cnt, (*ite).val->size, (*ite).val->peak);
  }
  print_header(f);
  fprintf(f, "\n");
}
} // namespace esmd
#ifdef MEM_TEST
int main() {
  // htab<cchar_key, memrec_t *> ht(&esmd::self_alloc);
  printf("%d %d\n", esmd::self_rec.size, esmd::self_rec.cnt);
  int *a = esmd::allocate<int>(50, "50 int");
  esmd::allocate<int>(50, "50 int");
  esmd::allocate<double>(50, "50 double");
  esmd::deallocate(a);
  esmd::print_memusage();
}
#endif
